package fatworm.storage.table;

import fatworm.query.constant.*;
import fatworm.storage.file.*;
import fatworm.storage.types.*;
import fatworm.tx.*;
import fatworm.util.*;

/**
 * Manages a file of records. There are methods for iterating through the
 * records and accessing their contents.
 * 
 * @author Edward Sciore
 */
public class HeapFile
{
    private TableInfo ti;
    private Transaction tx;
    private String filename;
    private TuplePage tp = null;
    private int currentblknum;

    public HeapFile(TableInfo ti, Transaction tx)
    {
        this.ti = ti;
        this.tx = tx;
        filename = ti.fileName();
        if (tx.size(filename) == 0)
            appendBlock();
        moveTo(0);
    }
    public void close()
    {
        tp.close();
    }
    public void beforeFirst()
    {
        moveTo(0);
    }
    public boolean next()
    {
        Lib.debug("HeapFile.next()");
        while (true)
        {
            if (tp.next())
                return true;
            if (atLastBlock())
                return false;
            moveTo(currentblknum + 1);
        }
    }
    public Constant getValue(String fldname)
    {
        Lib.debug("HeapFile.getValue" + " " + fldname);
        SType defVal = ti.schema().type(fldname);
        SType res;
        if (defVal == null)
            throw new Error("!!!");
        if (Types.isVarType(defVal.getType()))
        {
            byte[] s_vid = tp.getBytes(fldname);
            VID vid = getVID(s_vid);
            VarFile vf = new VarFile(ti, fldname, tx);
            vf.moveToVid(vid);
            byte[] bytes = vf.getBytes();
            for (int i = 0; i < bytes.length; i++)
                Lib.debug(i + ": " + bytes[i]);
            vf.close();
            res = defVal.newInstance();
            res.fromBytes(bytes);
            return res.toConstant();
        } else
        {
            byte[] bytes = tp.getBytes(fldname);
            res = defVal.newInstance();
            res.fromBytes(bytes);
            return res.toConstant();
        }
    }
    public void setValue(String fldname, Constant value)
    {
        Lib.debug("HeapFile.setValue()" + fldname + " " + value);
        SType defVal = ti.schema().type(fldname);
        if (defVal == null)
            throw new Error("!!!");
        SType val = defVal.newInstance();
        val.fromConstant(value);
        if (Types.isVarType(defVal.getType()))
        {
            VarFile vf = new VarFile(ti, fldname, tx);
            vf.insert(val.length());
            vf.setBytes(val.toBytes());
            for (int i = 0; i < val.toBytes().length; i++)
                Lib.debug(i + ": " + val.toBytes()[i]);
            VID vid = vf.currentVID();
            vf.close();
            byte[] s_vid = new byte[8];
            Lib.bytesFromInt(s_vid, 0, vid.blknum);
            Lib.bytesFromInt(s_vid, 4, vid.id);
            tp.setBytes(fldname, s_vid);
        } else
        {
            tp.setBytes(fldname, val.toBytes());
        }
    }
    /**
     * Deletes the current record. The client must call next() to move to the
     * next record. Calls to methods on a deleted record have unspecified
     * behavior.
     */
    public void delete()
    {
        tp.delete();
    }
    /**
     * Inserts a new, blank record somewhere in the file beginning at the
     * current record. If the new record does not fit into an existing block,
     * then a new block is appended to the file.
     */
    public void insert()
    {
        while (!tp.insert())
        {
            if (atLastBlock())
                appendBlock();
            moveTo(currentblknum + 1);
        }
    }
    /**
     * Positions the current record as indicated by the specified RID.
     * 
     * @param rid
     *            a record identifier
     */
    public void moveToRid(RID rid)
    {
        moveTo(rid.blknum);
        tp.moveToId(rid.id);
    }
    /**
     * Returns the RID of the current record.
     * 
     * @return a record identifier
     */
    public RID currentRid()
    {
        int id = tp.currentId();
        return new RID(currentblknum, id);
    }
    private void moveTo(int b)
    {
        if (tp != null)
            tp.close();
        currentblknum = b;
        Block blk = new Block(filename, currentblknum);
        tp = new TuplePage(blk, ti, tx);
    }
    private boolean atLastBlock()
    {
        Lib.debug("HeapFile.atLastBlock()" + " curBlk: " + currentblknum + " size: "
                + (tx.size(filename) - 1));
        return currentblknum == tx.size(filename) - 1;
    }
    private void appendBlock()
    {
        TuplePageFormatter fmtr = new TuplePageFormatter(ti);
        tx.append(filename, fmtr);
    }
    private VID getVID(byte[] bytes)
    {
        Lib.assertTrue(bytes.length == 8);
        int blknum = Lib.bytesToInt(bytes, 0);
        int id = Lib.bytesToInt(bytes, 4);
        return new VID(blknum, id);
    }
}